/*************************************************************
 * TwoSocks for network connection toy.
 *
 * This software utilize two sockets in modes of server and
 * client. Actually two sockets handle two connections.
 * 
 * (C) Copyright by Hojin Choi <hojin.choi@gmail.com>
 *
 * License: Apache License v2.0
 * Last Date: 2010.07.11
 * Version: 0.1
 *************************************************************/

#include "config.h"
#include "2socks.h"
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

#if HAVE_UNISTD_H
#include <unistd.h>
#endif

#if HAVE_STRINGS_H
#include <strings.h>
#endif

#if HAVE_STRING_H
#include <string.h>
#endif

#if HAVE_ARPA_INET_H
#include <arpa/inet.h>
#endif

#if HAVE_NETINET_IN_H
#include <netinet/in.h>
#endif

#if HAVE_SYS_QUEUE_H
#include <sys/queue.h>
#endif

#include <errno.h>

#define ts_make_as_nonblock( fd ) evutil_make_socket_nonblocking( fd )
#define verboselog if(ctx->opts->verbosity) printf
#define DECLARE_CTX_INDEX(ctx,index,cindex,check) \
	struct twosockctx * ctx = (struct twosockctx *) _twosockctx; \
	int index  = (check == ctx->sock[TS_FIRST].check ? TS_FIRST  : TS_SECOND); \
	int cindex = (1 - index)

static const char * state_strs[] = 
{
	"NONE_NONE",
	"ACCEPTING_NONE",
	"ACCEPTING_ACCEPTING",
	"CONNECTING_CONNECTING",
	"RECEIVING_RECEIVING",
	"RECEIVING_CONNECTING",
	"CONNECTING_RECEIVING",
	"RECEIVING_ACCEPTING",
	"ACCEPTING_RECEIVING",
	"CLOSED_RECEIVING",
	"RECEIVING_CLOSED"
};

const char * get_state_string( enum ts_state idx )
{
	if( idx < 0 || idx >= END_OF_STATE ) {
		return "(out of range)";
	}
	return state_strs[ idx ];
}

const char * engine_strs[] = 
{
	"NONE",
	"server-server",
	"server-client",
	"client-server",
	"client-client"
};

const char * get_engine_string( enum ts_engine idx )
{
	if( idx < 0 || idx >= END_OF_ENGINE ) {
		return "(out of range)";
	}
	return engine_strs[ idx ];
}


struct twosockctx * ts_alloc( int engine, int state, const struct options * opts )
{
	struct twosockctx * ctx = calloc( 1, sizeof (struct twosockctx) );

	if( !ctx )
	{
		return ctx;
	}
	ctx->engine = engine;
	ctx->state = state;

	memset( & ctx->sock, 0, sizeof ctx->sock );

	ctx->sock[0].fd = -1;
	ctx->sock[1].fd = -1;

	ctx->opts  = opts;
	return ctx;
}

void ts_add_event_accept( struct twosockctx * ctx, int index, void (*callback)(int, short, void *) )
{
	event_set( &ctx->sock[index].evt, ctx->sock[index].fd, EV_READ | EV_PERSIST, callback, ctx );
	event_add( &ctx->sock[index].evt, NULL );
}

void ts_add_event_connect( struct twosockctx * ctx, int index, void (*callback)(int, short, void *) )
{
	event_set( &ctx->sock[index].evt, ctx->sock[index].fd, EV_WRITE, callback, ctx );
	event_add( &ctx->sock[index].evt, NULL );
}

struct twosockctx * _last_ctx = NULL;

void ts_add_event_read( struct twosockctx * ctx, int index )
{
	if( ctx->sock[index].bevt ) {
		bufferevent_free( ctx->sock[index].bevt );
	}
	/* _last_ctx = ctx; */
	ctx->sock[index].bevt = bufferevent_new( ctx->sock[index].fd, ts_read, NULL, ts_error, ctx );
	bufferevent_enable( ctx->sock[index].bevt, EV_READ | EV_WRITE | EV_PERSIST );
}

void ts_free( struct twosockctx * ctx )
{
	ts_close( ctx );

	event_del( & ctx->sock[TS_FIRST ].evt );
	event_del( & ctx->sock[TS_SECOND].evt );

	if( ctx->sock[TS_FIRST].bevt ) {
		bufferevent_free( ctx->sock[TS_FIRST].bevt );
	}
	if( ctx->sock[TS_SECOND].bevt ) {
		bufferevent_free( ctx->sock[TS_SECOND].bevt );
	}

	if( ctx->ctxlist_head ) {
		free( ctx->ctxlist_head );
		ctx->ctxlist_head = NULL;
	}

	free( ctx );
}

int ts_queue_init( struct twosockctx * ctx )
{
	if( ctx->ctxlist_head ) {
		free( ctx->ctxlist_head );
	}
	ctx->ctxlist_head = (list_head_t) malloc( sizeof *ctx->ctxlist_head );
	if( !ctx->ctxlist_head ) {
		perror( "malloc" );
		return -1;
	}
	list_init( ctx->ctxlist_head );
}

void ts_dump_ctx( const struct twosockctx * ctx )
{
	printf( "CTX(%p): engine(%s) state(%s) sock1(%d) sock2(%d) opts(%p)\n", 
		ctx,
		get_engine_string(ctx->engine),
		get_state_string(ctx->state),
		ctx->sock[TS_FIRST].fd,
		ctx->sock[TS_SECOND].fd,
		ctx->opts
	);
}

void ts_dump_opts( const struct options * opts )
{
#define dump_hostinfos
	switch( opts->engine ) {
	case TS_SERVER_CLIENT:
	case TS_SERVER_SERVER:
	case TS_CLIENT_CLIENT:
	case TS_CLIENT_SERVER:
		printf( "OPT first, "
			"bindaddr(%u) bindport(%hu) ",
			((struct sockaddr_in * )( & opts->nodes[TS_FIRST].bindaddr ))->sin_addr.s_addr,
			((struct sockaddr_in * )( & opts->nodes[TS_FIRST].bindaddr ))->sin_port );
		printf( "OPT second, "
			"bindaddr(%u) bindport(%hu) ",
			((struct sockaddr_in * )( & opts->nodes[TS_SECOND].bindaddr ))->sin_addr.s_addr,
			((struct sockaddr_in * )( & opts->nodes[TS_SECOND].bindaddr ))->sin_port );
			break;
	default:
		assert( NULL && "What?" );
	}

}

void ts_dump_ctxlist( const struct twosockctx * ctx )
{
	const struct twosockctx * p;
	printf( "DUMP of queue......\n" );
	list_foreach( p, ctx->ctxlist_head ) {
		ts_dump_ctx( p );
	}
	printf( "DUMP of queue upto here ......\n" );
}

int ts_get_errno( int fd )
{
	int e;
	int len = sizeof e;
	if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &e, &len) < 0) {
		if( errno == ENOTSOCK ) {
			return 0;
		}
		return -1;
	}
	return e;
}

struct sockaddr_in ts_get_sockaddr_in( uint32_t bindaddr, uint16_t port )
{
	struct sockaddr_in addr;
	memset( &addr, 0, sizeof addr );
	addr.sin_family = AF_INET;
	addr.sin_addr.s_addr = htonl( bindaddr );
	addr.sin_port = htons( port );
	return addr;
}

int ts_create_socket( int sock_family, const struct sockaddr * addr, int len )
{
	int val = 1;
	int sock = -1;
	
	sock = socket( sock_family, SOCK_STREAM, 0 );
	if( sock < 0 )
	{
		perror( "socket" );
		exit(0);
	}

	ts_make_as_nonblock( sock );

	if( 0 > setsockopt( sock, SOL_SOCKET, SO_REUSEADDR, &val, sizeof val ) )
	{
		close(sock);
		return -1;
	}

	if( addr->sa_family != AF_UNSPEC && 0 > bind( sock, addr, len ) ) {
		perror( "bind" );
		close(sock);
		return -1;
	}
	ts_log_socket( sock, "socket-create" );
	return sock;
}

int ts_create_server( struct twosockctx * ctx, int index )
{
	ctx->sock[index].fd = ts_create_socket( 
		ctx->opts->nodes[index].family,
		& ctx->opts->nodes[index].bindaddr, 
		ctx->opts->nodes[index].len_bindaddr );

	ts_add_event_accept( ctx, index, ts_accept );

	if( 0 > listen( ctx->sock[index].fd, 5 ) )
	{
		perror( "listen" );
		close(ctx->sock[index].fd);
		return -1;
	}
	return 0;
}

int ts_create_client( struct twosockctx * ctx, int index )
{
	struct sockaddr_in addr;
	const struct ts_address * node = & ctx->opts->nodes[index];

	if( node->family == AF_PRIVATE_STDIO ) {
		ctx->sock[index].fd = node->inherited_fd;
		ts_add_event_connect( ctx, index, ts_connect );
		return 0;
	}
	ctx->sock[index].fd = ts_create_socket( 
		node->addr->ai_family, 
		& node->bindaddr, 
		node->len_bindaddr );

	ts_add_event_connect( ctx, index, ts_connect );
	if( 0 > connect( ctx->sock[index].fd, node->addr->ai_addr, node->addr->ai_addrlen ) )
	{
		if( errno != EINPROGRESS ) {
			perror( "connect" );
			close(ctx->sock[index].fd);
			return -1;
		}
	}
	return 0;
}

int ts_accept_socket( int fd )
{
	struct sockaddr_in addr;
	int nfd;
	socklen_t len = sizeof addr;

	memset( &addr, 0, sizeof addr );

	nfd = accept( fd, (struct sockaddr *) & addr, & len );
	if( nfd < 0 ) {
		perror( "Accept error" );
		return -1;
	}
	ts_make_as_nonblock( nfd );
	return nfd;
}

void ts_close( struct twosockctx * ctx )
{
	if( ctx->sock[TS_FIRST].fd >= 0 )
	{
		close( ctx->sock[TS_FIRST].fd );
		ctx->sock[TS_FIRST].fd= -1;
	}

	if( ctx->sock[TS_SECOND].fd >= 0 )
	{
		close( ctx->sock[TS_SECOND].fd );
		ctx->sock[TS_SECOND].fd= -1;
	}
}

void ts_relay( struct twosockctx * ctx )
{
	if( EVBUFFER_LENGTH( ctx->sock[TS_SECOND].bevt->input ) ) {
		bufferevent_write_buffer( ctx->sock[TS_FIRST ].bevt, ctx->sock[TS_SECOND].bevt->input );
	}
	if( EVBUFFER_LENGTH( ctx->sock[TS_FIRST].bevt->input ) ) {
		bufferevent_write_buffer( ctx->sock[TS_SECOND].bevt, ctx->sock[TS_FIRST ].bevt->input );
	}
}

void ts_flush( struct twosockctx * ctx )
{
	ts_relay(ctx);

	if( EVBUFFER_LENGTH( ctx->sock[TS_FIRST].bevt->output ) ) {
		evbuffer_write( ctx->sock[TS_FIRST].bevt->output, ctx->sock[TS_FIRST].fd );
	}
	if( EVBUFFER_LENGTH( ctx->sock[TS_SECOND].bevt->output ) ) {
		evbuffer_write( ctx->sock[TS_SECOND].bevt->output, ctx->sock[TS_SECOND].fd );
	}
}

static int ts_server_client_accept( struct twosockctx * ctx, int index )
{
	struct twosockctx * newctx;
	/* Emitting a new context */
	do
	{
		newctx = ts_alloc( ctx->engine, RECEIVING_CONNECTING, ctx->opts );
		if( !newctx ) {
			break;
		}
		/* ts_dump_ctx( newctx ); */

		/* Accept */
		newctx->sock[TS_FIRST].fd = ts_accept_socket( ctx->sock[TS_FIRST].fd );
		ts_add_event_read( newctx, TS_FIRST );
		ts_log_connection( newctx->sock[TS_FIRST].fd, "accept" );

		/* and Connecting */
		if( 0 > ts_create_client( newctx, TS_SECOND ) ) {
			break;
		}

		/* Normal return */
		return 0;
	} while(0);

	/* Error return */
	ts_close( ctx );
	ts_free( ctx );
	return -1;
}

static struct twosockctx * ts_list_search_waiting( struct twosockctx * ctx, int index )
{
	struct twosockctx * p;
	int searching_state;

	searching_state = index == TS_FIRST ? ACCEPTING_RECEIVING : RECEIVING_ACCEPTING;

	assert( ctx->ctxlist_head );

	list_foreach(p, ctx->ctxlist_head) {
		if( p->state == searching_state ) {
			return p;
		}
	}
	return NULL;
}

static int ts_server_server_accept( struct twosockctx * ctx, int index )
{
	int state;
	struct twosockctx * workctx;
	int cindex = index == TS_FIRST ? TS_SECOND : TS_FIRST;

	do
	{
		workctx = ts_list_search_waiting( ctx, index );
		if( !workctx ) {
			/* Emitting a new context */
			workctx = ts_alloc( ctx->engine, 
				index == TS_FIRST ? RECEIVING_ACCEPTING : ACCEPTING_RECEIVING, 
				ctx->opts );
			if( !workctx ) {
				break;
			}
			/* ts_dump_ctx( workctx ); */
			workctx->sock[index].fd = ts_accept_socket( ctx->sock[index].fd );
			ts_add_event_read( workctx, index );
			list_insert_head( ctx->ctxlist_head, workctx );
			ts_log_connection( workctx->sock[index].fd, "accept" );
		} else {
			/* Accept */
			list_remove( ctx->ctxlist_head, workctx );
			workctx->state = RECEIVING_RECEIVING;
			workctx->sock[index].fd = ts_accept_socket( ctx->sock[index].fd );
			ts_add_event_read( workctx, index );
			ts_relay( workctx );
			ts_log_connection( workctx->sock[index].fd, "accept" );
		}
		return 0;
	} while(0);

	/* Error return */
	ts_close( ctx );
	ts_free( ctx );
	return -1;
}

/* Callback of libevent dispatching */

void ts_accept( int fd, short event, void * _twosockctx )
{
	DECLARE_CTX_INDEX( ctx, index, cindex, fd );

	switch( ctx->engine ) {
	case TS_SERVER_CLIENT:
		assert( ctx->state == ACCEPTING_NONE );
		ts_server_client_accept( ctx, index );
		break;
	case TS_SERVER_SERVER:
		assert( ctx->state == ACCEPTING_ACCEPTING );
		ts_server_server_accept( ctx, index );
		ts_dump_ctxlist( ctx );
		break;
	default:
		fprintf( stderr, "Unknown engine/state: %d, %d\n", ctx->engine, ctx->state );
		break;
	}
}

void ts_connect( int fd, short event, void * _twosockctx )
{
	DECLARE_CTX_INDEX( ctx, index, cindex, fd );
	int e;

	/*ts_dump_ctx( ctx );*/

	if( (e=ts_get_errno( fd )) != 0 )
	{
		fprintf( stderr, "connect(%d): %s to %s\n", e,
			strerror(e), ctx->opts->args[index] );
		ts_close( ctx );
		return;
	}

	ts_log_connection( fd, "connect" );

	switch( ctx->state )
	{
	case CONNECTING_CONNECTING:
		ctx->state = RECEIVING_CONNECTING + index;
		ts_add_event_read( ctx, index );
		break;
	case CONNECTING_RECEIVING:
	case RECEIVING_CONNECTING:
		ctx->state = RECEIVING_RECEIVING;
		ts_add_event_read( ctx, index );
		ts_relay( ctx );
		break;
	default:
		assert( "Check me!" );
	}
	/* ts_dump_ctx( ctx ); */
}

void ts_read( struct bufferevent * bevt, void * _twosockctx )
{
	DECLARE_CTX_INDEX( ctx, index, cindex, bevt );

	int len;
	char buf[TS_BUFMAX];

	if( ctx->state != RECEIVING_RECEIVING ) {
		printf("Skip reading for waiting another party..\n");
		printf( "Buffer length: %d %d\n", 
			ctx->sock[index].bevt->input->totallen,
			ctx->sock[index].bevt->input->off );
		printf( "Buffer length: %d %d\n", 
			ctx->sock[index].bevt->output->totallen,
			ctx->sock[index].bevt->output->off );
			return;
	}
	do {
		len = bufferevent_read( bevt, buf, sizeof buf );
		if( len < 0 )
		{
			ts_close( ctx );
			ts_free( ctx );
			return;
		}
		
		bufferevent_write( ctx->sock[ cindex ].bevt, buf, len );
	} while( len > 0 );
}

void ts_error( struct bufferevent * bevt, short what, void * _twosockctx )
{
	DECLARE_CTX_INDEX(ctx,index,cindex,bevt);
	ts_flush(ctx);
	ts_close(ctx);
	ts_free(ctx);
}

struct event_base * evbase;
struct event timer;
struct timeval tv = {3L,0L};

void ts_report_status( int fd, short event, void * arg )
{
	evtimer_add( &timer, &tv );
	if( _last_ctx == NULL ) {
		return;
	}
	printf("Buffer status 1 read:(%d) write(%d)\n", 
		EVBUFFER_LENGTH( EVBUFFER_INPUT(_last_ctx->sock[0].bevt) ),
		EVBUFFER_LENGTH( EVBUFFER_OUTPUT(_last_ctx->sock[0].bevt) ) );
	printf("Buffer status 2 read:(%d) write(%d)\n", 
		EVBUFFER_LENGTH( EVBUFFER_INPUT(_last_ctx->sock[1].bevt) ),
		EVBUFFER_LENGTH( EVBUFFER_OUTPUT(_last_ctx->sock[1].bevt) ) );
}

void ts_event_init()
{
	evbase = event_init();
}

/* ENGINES ----------------------------------------------------------*/

void ts_dispatch( struct twosockctx * ctx )
{

	ts_log( "start %s mode\n", get_engine_string( ctx->engine ) );
	/*
	evtimer_set( &timer, ts_report_status, NULL );

	evtimer_add( &timer, &tv );
	*/
	event_dispatch();
}

/* -----------------------------------------------------------------------
 -sc : Server-and-Client Engine
           +-----------+
           | Acpt-None |
           +-----------+
                 | (emit)
                 V
           +-----------+
           | Recv-Conn |
           +-----------+
                 |
                 V
           +-----------+
           | Recv-Recv | (running)
           +-----------+
----------------------------------------------------------------------- */

void server_client( const struct options * opts )
{
	struct twosockctx * ctx;

	ts_event_init();
	ctx = ts_alloc( TS_SERVER_CLIENT, ACCEPTING_NONE, opts );
	if( !ctx )
	{
		perror( "Can't allocate context" );
		return;
	}

	if( 0 > ts_create_server( ctx, TS_FIRST ) )
	{
		return;
	}

	ts_dispatch( ctx );
	ts_free( ctx );
}

/* -----------------------------------------------------------------------
 -cc : Client-and-Client Engine
           +-----------+
           | Conn-Conn |
           +-----------+
                 |
                 +--------------+
                 |              |
                 V              V
           +-----------+  +-----------+
           | Recv-Conn |  | Conn-Recv | (Waiting for party's connection)
           +-----------+  +-----------+
                 |              |
                 +--------------+
                 |
                 V
           +-----------+
           | Recv-Recv | (running)
           +-----------+
----------------------------------------------------------------------- */

void client_client( const struct options * opts )
{
	struct twosockctx * ctx;

	ts_event_init();
	ctx = ts_alloc( TS_CLIENT_CLIENT, CONNECTING_CONNECTING, opts );

	if( !ctx ) {
		perror( "Can't allocate context" );
		return;
	}

	ts_create_client( ctx, TS_FIRST );
	if( ctx->sock[TS_FIRST].fd < 0 ) {
		ts_free( ctx );
		return;
	}
	ts_create_client( ctx, TS_SECOND );
	if( ctx->sock[TS_SECOND].fd < 0 ) {
		ts_close( ctx );
		ts_free( ctx );
		return;
	}
	/* ts_dump_ctx( ctx ); */

	ts_dispatch( ctx );
}

/* -----------------------------------------------------------------------
 -ss : Server-and-Server Engine
           +-----------+
           | Acpt-Acpt |
           +-----------+
              |     |
              |     +-----------+
              |                 |
   (Queueing) V                 |
           +-----------+        |
           | Recv-Wait |--------+       (Waiting for party's acception)
           +-----------+        |      
           +-----------+        |      
           | Recv-Wait |        |       (Waiting for party's acception)
           +-----------+        |      
           +-----------+        |      
           | Recv-Wait |        |       (Waiting for party's acception)
           +-----------+        |      
                 .              |
                 .              |
                                |
                 +--------------+
                 |
                 V
           +-----------+
           | Recv-Recv | (running)
           +-----------+
 Items in the queue may varies by the client acception sequences between
 [Recv-Wait] and [Wait-Recv], but the queue does not contain both types
 in same time.
----------------------------------------------------------------------- */

void server_server( const struct options * opts )
{
	struct twosockctx * ctx;

	ts_event_init();
	ctx = ts_alloc( TS_SERVER_SERVER, ACCEPTING_ACCEPTING, opts );
	if( !ctx )
	{
		perror( "Can't allocate context" );
		return;
	}

	ts_queue_init( ctx );

	if( 0 > ts_create_server( ctx, TS_FIRST )
	 || 0 > ts_create_server( ctx, TS_SECOND ) )
	{
		ts_free( ctx );
		return;
	}

	ts_dispatch( ctx );
	ts_free( ctx );
}

void client_server( const struct options * opts )
{
}
